package horncomb.bpm.process;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.util.Assert;

import horncomb.bpm.BpmContextHolder;
import horncomb.bpm.process.core.NestedToken;
import horncomb.bpm.process.core.ProcessDefinition;
import horncomb.bpm.process.core.ProcessInstance;
import horncomb.bpm.process.core.ProcessInstanceCreator;
import horncomb.bpm.process.core.Token;
import horncomb.bpm.process.core.Transition;
import horncomb.bpm.process.instance.DefaultProcessInstanceCreator;
import horncomb.bpm.process.instance.DefaultTokenCreator;
import horncomb.bpm.process.instance.TokenCreator;
import horncomb.bpm.task.core.TaskInstance;

/**
 * @author Daniel
 */
public class SimpleProcessManager implements ProcessManager {
	private Map<String, ProcessDefinition> processDefinitions = new HashMap<String, ProcessDefinition>();
	private Map<String, ProcessInstance> processInstanceCache = new HashMap<String, ProcessInstance>();
	private Map<String, Token> tokenCache = new HashMap<String, Token>();

	private TokenCreator defaultTokenCreator = new DefaultTokenCreator();
	private ProcessInstanceCreator defaultProcessInstanceCreator = new DefaultProcessInstanceCreator();

	// private DefaultProcessInstanceDao processInstanceDao;
	// private DefaultTokenDao tokenDao;

	public SimpleProcessManager() {

	}

	/**
	 * @param processDefinition
	 */
	public void deploy(ProcessDefinition processDefinition) {
		if (this.processDefinitions.containsKey(processDefinition.getId())) {
			// TODO ProcessDefinitionExsitException;
		}
		this.processDefinitions.put(processDefinition.getId(),
				processDefinition);
	}

	public ProcessDefinition findProcessDefinition(String id) {
		if (!this.processDefinitions.containsKey(id)) {
			// TODO ProcessDefinitionNotFoundException;
		}
		return this.processDefinitions.get(id);
	}

	/**
	 * {@inheritDoc}
	 */
	public ProcessInstance start(String id, Object contextModel) {
		return this.start(null, id, contextModel);
	}

	/**
	 * {@inheritDoc}
	 */
	public ProcessInstance start(Token parentToken, String id,
			Object contextModel) {
		ProcessDefinition procDef = BpmContextHolder.getContext()
				.getProcessManagementService().findProcessDefinition(id);
		ProcessInstance procInst = procDef.newInstance(parentToken,
				contextModel);
		procInst.start(contextModel);
		return procInst;
	}

	public ProcessInstance invokeCreateProcessInstance(Token parentToken,
			ProcessDefinition processDefinition, Object contextModel,
			ProcessInstanceCreator creator) {
		if (creator == null) {
			creator = this.defaultProcessInstanceCreator;
		}
		ProcessInstance instance = creator.create(parentToken,
				processDefinition, contextModel);
		// this.processInstanceDao.insert(instance);
		this.processInstanceCache.put(instance.getId(), instance);
		this.tokenCache.put(instance.getToken().getId(), instance.getToken());
		return instance;
	}

	/**
	 * {@inheritDoc}
	 */
	public Token invokeCreateToken(String processInstanceId, Token parent,
			Object contextModel, TokenCreator creator) {
		if (creator == null) {
			creator = this.defaultTokenCreator;
		}
		Token token = creator.create(processInstanceId, parent, contextModel);
		// this.tokenDao.insert(token);
		this.tokenCache.put(token.getId(), token);
		return token;
	}

	/**
	 * {@inheritDoc}
	 */
	public Token findToken(String id) {
		Token token = this.tokenCache.get(id);
		if (token == null) {
			// token = this.tokenDao.getById(id);
			// if (token != null) {
			// this.tokenCache.put(token.getId(), token);
			// } else {
			// // TODO throw Exception;
			// }
		}
		return token;
	}

	public List<? extends Token> findSubTokens(String parentId) {
		Assert.hasText(parentId);
		// List<Token> result = this.tokenDao.findSubTokens(parentId);
		List<Token> result = new ArrayList<Token>();
		for (Token t : this.tokenCache.values()) {
			if (t instanceof NestedToken) {
				Token parent = ((NestedToken) t).getParent();
				if (parent != null && parentId.equals(parent.getId())) {
					result.add(t);
				}
			}
		}
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	public Token findTokenByBizEntityId(String bizEntityId) {
		for (Token token : this.tokenCache.values()) {
			if (bizEntityId.equals(token.getEntity().getBizEntityId())) {
				return token;
			}
		}
		// TODO throw Exception
		return null;
	}

	/**
	 * {@inheritDoc}
	 */
	public Map<String, ? extends Token> findTokenByBizEntityIds(
			Collection<String> bizEntityIds) {
		Map<String, Token> tokenMap = new HashMap<String, Token>();
		for (Token token : this.tokenCache.values()) {
			for (String bizEntityId : bizEntityIds) {
				if (bizEntityId.equals(token.getEntity().getBizEntityId())) {
					tokenMap.put(bizEntityId, token);
				}
			}
		}
		return tokenMap;
	}

	/**
	 * {@inheritDoc}
	 */
	public ProcessInstance findProcessInstance(String id) {
		ProcessInstance procInst = this.processInstanceCache.get(id);
		if (procInst == null) {
			// procInst = this.processInstanceDao.getById(id);
			// if (procInst != null) {
			// this.processInstanceCache.put(procInst.getId(), procInst);
			// } else {
			// // TODO throw Exception;
			// }
		}
		return this.processInstanceCache.get(id);
	}

	/**
	 * TODO change ?? {@inheritDoc}
	 */
	public List<? extends ProcessInstance> findNestedProcessInstance(
			String procInstId) {
		Assert.hasText(procInstId);
		List<ProcessInstance> procs = new ArrayList<ProcessInstance>();
		for (Token stateDesc : this.tokenCache.values()) {
			if (stateDesc instanceof NestedToken) {
				Token parent = ((NestedToken) stateDesc).getParent();
				if (parent != null
						&& procInstId.equals(parent.getEntity()
								.getProcessInstanceId())) {
					procs.add(this.processInstanceCache.get(stateDesc
							.getProcessInstance().getId()));
				}
			}
		}
		return procs;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean hasNestedProcessInstance(String procInstId) {
		Assert.hasText(procInstId);
		for (Token stateDesc : this.tokenCache.values()) {
			if (stateDesc instanceof NestedToken) {
				Token parent = ((NestedToken) stateDesc).getParent();
				if (parent != null
						&& procInstId.equals(parent.getEntity()
								.getProcessInstanceId())) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * {@inheritDoc}
	 */
	public int countSubTokenInState(String parentTokenId, String[] stateIds) {
		Assert.hasText(parentTokenId);
		Assert.notEmpty(stateIds);
		List<Token> result = new ArrayList<Token>();
		for (Token t : this.tokenCache.values()) {
			if (t instanceof NestedToken) {
				Token parent = ((NestedToken) t).getParent();
				if (parent != null && parentTokenId.equals(parent.getId())) {
					for (String stateId : stateIds) {
						if (t.getState().getId().equals(stateId)) {
							result.add(t);
							break;
						}
					}
				}
			}
		}
		return result.size();
	}

	/**
	 * {@inheritDoc}
	 */
	public int countSubTokens(String parentId) {
		return this.findSubTokens(parentId).size();
	}

	/**
	 * {@inheritDoc}
	 */
	public void performProcessTransition(String processInstanceId,
			String transitionId, Object args) {
		ProcessInstance procInst = this.findProcessInstance(processInstanceId);
		Transition transition = procInst.getState().getProcessDefinition()
				.findTransition(transitionId);
		procInst.getToken().performTransition(transition, args);
		// TODO instance persistent
	}

	public void performTransition(String tokenId, String transitionId,
			Object args) {
		Token token = this.findToken(tokenId);
		Transition transition = token.getState().getProcessDefinition()
				.findTransition(transitionId);
		token.performTransition(transition, args);
	}

	/**
	 * {@inheritDoc}
	 */
	public void update(Token token) {
	}

	/**
	 * {@inheritDoc}
	 */
	public void update(ProcessInstance processInstance) {
	}

	/**
	 * {@inheritDoc}
	 */
	public void deleteProcessInstance(String procInstId) {
		ProcessInstance procInst = this.findProcessInstance(procInstId);
		this.deleteProcessInstance(procInst);
	}

	protected void deleteProcessInstance(ProcessInstance procInst) {
		this.deleteProcessInstanceRecursive(procInst.getId());
		this.deleteToken(procInst.getEntity().getTokenId());
		this.processInstanceCache.remove(procInst.getId());
	}

	protected void deleteProcessInstanceRecursive(String procInstId) {
		List<? extends ProcessInstance> nestedProcInsts = this
				.findNestedProcessInstance(procInstId);
		if (nestedProcInsts != null && !nestedProcInsts.isEmpty()) {
			for (ProcessInstance inst : nestedProcInsts) {
				this.deleteProcessInstance(inst);
			}
		}
	}

	/**
	 * 删除token，包括其下的子token
	 * @param tokenId
	 */
	public void deleteToken(String tokenId) {
		this.deleteTokenRecursive(tokenId);
		this.doDeleteToken(tokenId);
	}

	protected void deleteTokenRecursive(String tokenId) {
		List<? extends Token> subTokens = this.findSubTokens(tokenId);
		if (subTokens != null && !subTokens.isEmpty()) {
			for (Token token : subTokens) {
				this.deleteToken(token.getId());
			}
		}
	}

	/**
	 * 删除token本身，以及token下的任务
	 * @param tokenId
	 */
	protected void doDeleteToken(String tokenId) {
		if (BpmContextHolder.getContext().getTaskManagementServcie() != null) {
			List<? extends TaskInstance> taskInsts = BpmContextHolder
					.getContext().getTaskManagementServcie()
					.findTaskInstancesByTokenId(tokenId);
			if (taskInsts != null && !taskInsts.isEmpty()) {
				for (TaskInstance taskInst : taskInsts) {
					BpmContextHolder.getContext().getTaskManagementServcie()
							.deleteTaskInstance(taskInst.getId());
				}
			}
		}
		this.tokenCache.remove(tokenId);
	}
}
